using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace Charisma
{
    public class IndexedSet<TKey, TItem> : IEnumerable<TItem> where TItem : class where TKey : class
    {
        protected readonly Dictionary<TKey, TItem> Items = new Dictionary<TKey, TItem>();
        private bool isReadOnly;

        public IndexedSet(Func<TItem, TKey> keyReader)
        {
            Contract.Requires(keyReader != null);

            KeyReader = keyReader;
        }

        protected Func<TItem, TKey> KeyReader { get; private set; }

        public int Count
        {
            get { return Items.Count; }
        }

        public bool IsReadOnly
        {
            get { return isReadOnly; }
            set
            {
                Contract.Requires(!IsReadOnly);
                isReadOnly = value;
            }
        }

        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(Items != null);
            Contract.Invariant(KeyReader != null);
        }

        public TItem Find(TKey key)
        {
            Contract.Requires(key != null);

            TItem item;

            if (!Items.TryGetValue(key, out item))
                return null;

            return item;
        }

        public bool Contains(TKey key)
        {
            return Items.ContainsKey(key);
        }

        public void Add(TItem item)
        {
            Contract.Requires(!IsReadOnly);
            Contract.Requires(item != null);

            var key = KeyReader(item);
            Contract.Assume(key != null);

            var result = Find(key);
            if (result != null)
            {
                // The same item
                if (result == item)
                    return;
                if (result != item)
                    throw new InvalidOperationException(string.Format("Entry for {0} already exists", key));
            }

            Items[key] = item;
            OnAdd(item);
        }

        public void Clear()
        {
            Contract.Requires(!IsReadOnly);

            Items.Clear();
            OnClear();
        }

        public bool Contains(TItem item)
        {
            Contract.Requires(item != null);
            var key = KeyReader(item);
            Contract.Assume(key != null);

            return Find(key) != null;
        }

        public bool Remove(TItem item)
        {
            Contract.Requires(!IsReadOnly);
            Contract.Requires(item != null);

            var key = KeyReader(item);
            Contract.Assume(key != null);

            bool result = Items.Remove(key);
            if (result)
                OnRemove(item);
            return result;
        }

        protected virtual void OnAdd(TItem item)
        {
        }

        protected virtual void OnRemove(TItem item)
        {
        }

        protected virtual void OnClear()
        {
        }

        #region Implementation of IEnumerable

        public IEnumerator<TItem> GetEnumerator()
        {
            return Items.Values.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }
}